package com.strong.utils;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.*;
import com.strong.utils.enum_.IdTypeEnum;
import com.strong.utils.exception.StrongRuntimeException;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.regex.Pattern;

import static com.strong.utils.jpa.JpaConstants.STR_INCLUDE_PROPERTIES_NAME;

/**
 * Strong工具类
 *
 * @author Administrator
 * @date 2022/01/04
 */
public class StrongUtils {

    // =================== 文件相关  ===================

    /**
     * 获取当前项目的静态路径
     * 1.以jar运行时获取jar文件所在目录
     * 2.开发模式时获取项目根目录
     *
     * @return {@link String}
     */
    public static String getLocalStaticPath() {
        String strWebRoot = FileUtil.getWebRoot().getAbsolutePath();
        return StrUtil.endWithIgnoreCase(strWebRoot, ".jar!") ? FileUtil.getWebRoot().getParent() : strWebRoot;
    }

    /**
     * 根据相对路径数组，获取实际路径
     *
     * @param strsPath 相对路径数组
     * @return {@link String}
     */
    public static String getStaticPath(String... strsPath) {
        if (ArrayUtil.isNotEmpty(strsPath)) {
            return StrUtil.join(File.separator, StrongUtils.getLocalStaticPath(), strsPath);
        } else {
            return StrongUtils.getLocalStaticPath();
        }
    }

    // =================== Bean相关  ===================

    /**
     * 对象或Map转Bean 默认忽略空值
     *
     * @param source            原Bean
     * @param clazz             目标的Bean类型
     * @param includeProperties 包括的属性数组
     * @return {@link T}
     */
    public static <T> T toBean(Object source, Class<T> clazz, String... includeProperties) {
        return toBean(source, clazz, true, includeProperties);
    }

    /**
     * 对象或Map转Bean
     *
     * @param source            原Bean
     * @param clazz             目标的Bean类型
     * @param ignoreNullValue   是否忽略空值
     * @param includeProperties 包括的属性数组
     * @return {@link T}
     */
    public static <T> T toBean(Object source, Class<T> clazz, boolean ignoreNullValue, String... includeProperties) {
        if (ArrayUtil.isNotEmpty(includeProperties)) {
            return BeanUtil.toBean(source, clazz, getCopyOptionsInclude(includeProperties).setIgnoreNullValue(ignoreNullValue));
        } else {
            return BeanUtil.toBean(source, clazz);
        }
    }

    /**
     * 比较两个对象的所有内容 完全相同（忽略static）
     * ——所有字段必须完全一致
     *
     * @param objA            DTO
     * @param objB            VO
     * @param strsIgnoreField 忽略字段数组
     */
    public static void compareBean(Object objA, Object objB, String... strsIgnoreField) {
        Field[] fields = ReflectUtil.getFields(objA.getClass());
        for (Field field : fields) {
            if (Modifier.isStatic(field.getModifiers())) {
                continue;
            }
            String strFiledName = field.getName();

            // 忽略字段，以$开头的可能是hibernate注入的字段
            if (StrUtil.equalsAny(strFiledName, strsIgnoreField) ||
                    StrUtil.startWith(strFiledName, "$")) {
                continue;
            }

            compareProperty(objB, objA, strFiledName);
            compareProperty(objA, objB, strFiledName);
        }
    }

    /**
     * 比较两个对象的指定字段内容
     *
     * @param objA         obja
     * @param objB         objb
     * @param strFiledName STR文件名称
     */
    public static void compareProperty(Object objA, Object objB, String strFiledName) {
        // 反射获取VO字段的值
        Object propertyA = getFieldValueByGet(objA, strFiledName);
        if (ObjUtil.isNotNull(propertyA)) {
            // 反射获取DTO字段的值
            Object propertyB = getFieldValueByGet(objB, strFiledName);
            // 断言DTO的字段非空
            Assert.notNull(propertyB, "：\nB对象：{}\nJSON：[{}]\n字段[{}]为空",
                    objB.getClass(), JSON.toJSONString(objB), strFiledName);

            // 将sql.date强制转为unit.date
            if (propertyA instanceof java.sql.Timestamp) {
                propertyA = DateUtil.date((java.sql.Timestamp) propertyA).toJdkDate();
            }
            if (propertyA instanceof java.sql.Date) {
                propertyA = DateUtil.date((java.sql.Date) propertyA).toJdkDate();
            }

            if (propertyB instanceof java.sql.Timestamp) {
                propertyB = DateUtil.date((java.sql.Timestamp) propertyB).toJdkDate();
            }
            if (propertyB instanceof java.sql.Date) {
                propertyB = DateUtil.date((java.sql.Date) propertyB).toJdkDate();
            }

            // 断言DTO和VO类型一致
            Assert.equals(propertyB.getClass(), propertyA.getClass(),
                    "\nA对象{}[{}]\nB对象{}[{}]\n字段[{}] 对象类型不一致",
                    objA.getClass(), propertyA.getClass(), objB.getClass(), propertyB.getClass(), strFiledName);

            // 如果DTO VO的类型为日期，则只匹配到“日”
            if (propertyA instanceof Date dateA) {
                Date dateB = (Date) propertyB;
                Assert.isTrue(DateUtil.isSameDay(dateB, dateA),
                        "\nA对象{}[{}]\nB对象{}[{}]\n字段[{}] 日期不同",
                        objA.getClass(), DateUtil.formatDateTime(dateA), objB.getClass(), DateUtil.formatDateTime(dateB), strFiledName);
            } else {
                Assert.equals(propertyB, propertyA,
                        "\nA对象{}[{}]\nB对象{}[{}]\n字段[{}] 内容不同",
                        objA.getClass(), propertyA, objB.getClass(), propertyB, strFiledName);
            }
        }
    }

    /**
     * 复制集合中的Bean属性 默认忽略空值
     *
     * @param collection        原Bean集合
     * @param targetType        目标Bean类型
     * @param includeProperties 包括的属性数组
     * @return {@link T}
     */
    public static <T> List<T> copyToList(Collection<?> collection, Class<T> targetType, String... includeProperties) {
        return copyToList(collection, targetType, true, includeProperties);
    }

    /**
     * 复制集合中的Bean属性
     *
     * @param collection        原Bean集合
     * @param targetType        目标Bean类型
     * @param ignoreNullValue   是否忽略空值
     * @param includeProperties 包括的属性数组
     * @return {@link T}
     */
    public static <T> List<T> copyToList(Collection<?> collection, Class<T> targetType, boolean ignoreNullValue, String... includeProperties) {
        if (ArrayUtil.isNotEmpty(includeProperties)) {
            return BeanUtil.copyToList(collection, targetType, getCopyOptionsInclude(includeProperties).setIgnoreNullValue(ignoreNullValue));
        } else {
            return BeanUtil.copyToList(collection, targetType);
        }
    }

    /**
     * 复制Bean对象属性 默认忽略空值
     *
     * @param source            源Bean对象
     * @param target            目标Bean对象
     * @param includeProperties 包括的属性数组
     */
    public static void copyProperties(Object source, Object target, String... includeProperties) {
        copyProperties(source, target, true, includeProperties);
    }

    /**
     * 复制Bean对象属性
     *
     * @param source            源Bean对象
     * @param target            目标Bean对象
     * @param ignoreNullValue   是否忽略空值
     * @param includeProperties 包括的属性数组
     */
    public static void copyProperties(Object source, Object target, boolean ignoreNullValue, String... includeProperties) {
        BeanUtil.copyProperties(source, target, getCopyOptionsInclude(includeProperties).setIgnoreNullValue(ignoreNullValue));
    }

    /**
     * 获得包括的属性数组CopyOptions
     *
     * @param includeProperties 包括属性
     * @return {@link CopyOptions}
     */
    public static CopyOptions getCopyOptionsInclude(String... includeProperties) {
        if (ArrayUtil.isNotEmpty(includeProperties)) {
            return CopyOptions.create().setPropertiesFilter((field, o) -> ArrayUtil.contains(includeProperties, field.getName()));
        } else {
            return CopyOptions.create();
        }
    }

    /**
     * 根据class获取其中的字段名数组
     *
     * @param classz classz
     * @return {@link String[] }
     */
    public static String[] getFiledNames(Class<?> classz) {
        List<String> listExclusionField = new ArrayList<>();
        Field[] fields = ReflectUtil.getFields(classz);
        for (Field field : fields) {
            // 不是static 加入到排除队列
            if (!Modifier.isStatic(field.getModifiers())) {
                listExclusionField.add(field.getName());
            }
        }
        return listExclusionField.toArray(new String[0]);
    }

    /**
     * 根据常量 STR_INCLUDE_PROPERTIES_NAME 获取排除字段
     *
     * @param classz classz
     * @return {@link List }<{@link String }>
     */
    public static String[] getExclusionFieldNames(Class<?> classz) {
        // 获取STRS_INCLUDE_PROPERTIES的常量内容
        Object obj = BeanUtil.getFieldValue(classz, STR_INCLUDE_PROPERTIES_NAME);
        // 如果获取到的对象为空 或者不是 String[] 类型，返回空的数组
        if (ObjUtil.isNull(obj) || !(obj instanceof String[] strsIncludeProperties)) {
            return new String[]{};
        }

        List<String> listExclusionField = new ArrayList<>();
        Field[] fields = ReflectUtil.getFields(classz);
        for (Field field : fields) {
            // 不是static 不在STRS_INCLUDE_PROPERTIES范围内的，加入到排除队列
            if (!Modifier.isStatic(field.getModifiers()) &&
                    !ArrayUtil.contains(strsIncludeProperties, field.getName())) {
                listExclusionField.add(field.getName());
            }
        }
        return listExclusionField.toArray(new String[0]);
    }

    /**
     * 根据字段名获取其整数类型内容 返回整数
     *
     * @param objModel     obj模型
     * @param strFiledName STR文件名称
     * @return {@link Integer }
     */
    public static Integer getIntByFiledName(Object objModel, String strFiledName) {
        return getByFiledName(objModel, strFiledName, Integer.class);
    }

    /**
     * 根据字段名获取其整数类型内容 返回字符串
     *
     * @param objModel     obj模型
     * @param strFiledName STR文件名称
     * @return {@link Integer }
     */
    public static String getStringByFiledName(Object objModel, String strFiledName) {
        return getByFiledName(objModel, strFiledName, String.class);
    }

    /**
     * 根据字段名获取其整数类型内容
     *
     * @param objModel     obj模型
     * @param strFiledName STR文件名称
     * @return {@link Integer }
     */
    public static <T> T getByFiledName(Object objModel, String strFiledName, Class<T> classz) {
        Object objFieldValue = getFieldValueByGet(objModel, strFiledName);
        Assert.notNull(objFieldValue, "【{}】字段为空\n{}", strFiledName, JSON.toJSONString(objModel));
        Assert.isTrue(objFieldValue.getClass().equals(classz),
                "【{}】字段类型不为【{}】", strFiledName, classz);
        return (T) objFieldValue;
    }

    /**
     * 按字段名获取方法
     *
     * @param strFiledName STR文件名称
     * @return {@link String }
     */
    public static String getMethodByFiledName(String strFiledName) {
        Assert.notBlank(strFiledName);
        return String.format("get%s", StrUtil.upperFirst(strFiledName));
    }

    /**
     * 通过反射Get获取字段值
     *
     * @param bean             豆
     * @param fieldNameOrIndex 字段名或索引
     * @return {@link Object }
     */
    public static Object getFieldValueByGet(Object bean, String fieldNameOrIndex) {
        return ReflectUtil.invoke(bean, StrongUtils.getMethodByFiledName(fieldNameOrIndex));
    }

    /**
     * 断言属性不为空
     *
     * @param source     源对象
     * @param properties 属性名数组
     */
    public static void assertPropertiesNotNull(Object source, String... properties) {
        Assert.isTrue(ArrayUtil.isNotEmpty(properties), "属性数组为空");
        for (String property : properties) {
            Assert.notNull(ReflectUtil.getFieldValue(source, property), "【{}】字段为空", property);
        }
    }

    /**
     * 断言仅所列属性不为空，其他属性为空
     *
     * @param source     源对象
     * @param properties 属性名数组
     */
    public static void assertOnlyPropertiesNotNull(Object source, String... properties) {
        assertPropertiesNotNull(source, properties);

        Field[] fields = ReflectUtil.getFields(source.getClass());
        for (Field field : fields) {
            String filedName = field.getName();
            if (!Modifier.isStatic(field.getModifiers()) && !ArrayUtil.contains(properties, filedName)) {
                Assert.isNull(ReflectUtil.getFieldValue(source, filedName), "【{}】字段非空", filedName);
            }
        }
    }

    // =================== 字符串相关  ===================

    /**
     * 过滤数组
     *
     * @param arraySource 源数组
     * @param arrayIgnore 忽略的数组
     * @return {@link T[]}
     */
    public static <T> T[] ignoreArray(T[] arraySource, T... arrayIgnore) {
        return ArrayUtil.filter(arraySource, strProperty -> !ArrayUtil.contains(arrayIgnore, strProperty));
    }

    // =================== 集合相关  ===================

    /**
     * 替换所有特殊字符
     *
     * @param strInput str输入
     * @return {@link String}
     */
    public static String replaceAllSpecialCharacter(String strInput) {
        return ReUtil.replaceAll(strInput, "[~!@#$%^&*()_+|}{\":?><`=\\-\\[\\]\\\\;',./]+", "").trim();
    }

    // =================== 随机相关  ===================

    /**
     * 得到随机ID
     *
     * @return int
     */
    public static int getRandomID() {
        return RandomUtil.randomInt(10000000, 99999999);
    }

    // =================== 数据库相关  ===================

    /**
     * 回调检查重复id
     *
     * @author simen
     * @date 2022/04/06
     */
    public interface CallbackCheckRepeatId {
        int getRepeatCount(int intId);
    }

    /**
     * 获取无重复的随机ID
     *
     * @param callbackCheckRepeatId 回调检查重复id
     * @return int
     */
    public static int getNoRepeatId(CallbackCheckRepeatId callbackCheckRepeatId) {
        int intId = StrongUtils.getRandomID();
        if (callbackCheckRepeatId != null) {
            while (callbackCheckRepeatId.getRepeatCount(intId) > 0) {
                intId = StrongUtils.getRandomID();
            }
        }
        return intId;
    }

    // =================== 枚举类相关  ===================

    /**
     * 比较枚举类的值是否存在
     *
     * @param clazz     clazz
     * @param fieldName 字段名
     * @param objValue  obj价值
     * @return {@link Boolean}
     */
    public static Boolean containsEnum(Class<? extends Enum<?>> clazz, String fieldName, Object objValue) {
        if (ObjUtil.isNull(objValue) || ObjUtil.isNull(clazz) || StrUtil.isBlank(fieldName)) {
            return false;
        } else {
            List<Object> listObject = EnumUtil.getFieldValues(clazz, fieldName);
            return CollUtil.contains(listObject, objValue);
        }
    }

    /**
     * 比较枚举类的值是否存在 默认value
     *
     * @param clazz    clazz
     * @param objValue obj价值
     * @return {@link Boolean}
     */
    public static Boolean containsEnumByValue(Class<? extends Enum<?>> clazz, Object objValue) {
        return containsEnum(clazz, "value", objValue);
    }

    /**
     * 比较枚举类的值是否存在 默认code
     *
     * @param clazz   clazz
     * @param objCode obj价值
     * @return {@link Boolean}
     */
    public static Boolean containsEnumByCode(Class<? extends Enum<?>> clazz, Object objCode) {
        return containsEnum(clazz, "code", objCode);
    }

    // =================== 对象转换相关  ===================

    /**
     * 将特定格式的日期转换为Date对象
     *
     * @param dateStr str日期
     * @param format  格式
     * @return {@link DateTime}
     */
    public static DateTime parseDateDefaultZone(CharSequence dateStr, String format) {
        return DateUtil.parse(dateStr, format, Locale.getDefault());
    }

    // =================== 文本格式校对相关  ===================

    /**
     * 使用正则表达式测试匹配
     *
     * @param strText  测试文本
     * @param strRegex 正则表达式
     * @return {@link Boolean }
     */
    public static Boolean isMatch(String strText, String strRegex) {
        if (StrUtil.hasBlank(strText, strRegex)) {
            return false;
        } else {
            return Pattern.compile(strRegex).matcher(strText).find();
        }
    }

    /**
     * 根据证件类型代码字符串 验证各类ID
     *
     * @param strID       ID
     * @param strTypeEnum 证件类型代码
     * @return {@link Boolean }
     */
    public static Boolean isIdCode(String strID, String strTypeEnum) {
        if (StrUtil.hasBlank(strID, strTypeEnum)) {
            throw new StrongRuntimeException("证件类型不符合规范");
        }

        IdTypeEnum idTypeEnum = null;
        // 遍历根据类型编码获取 证件类型枚举类
        for (IdTypeEnum tempType : IdTypeEnum.values()) {
            if (strTypeEnum.equals(tempType.getCode())) {
                idTypeEnum = tempType;
            }
        }

        // 如果证件类型枚举类存在 则调用验证，否则抛出异常
        if (ObjUtil.isNotNull(idTypeEnum)) {
            return isIdCode(strID, idTypeEnum);
        } else {
            throw new StrongRuntimeException("证件类型不存在");
        }
    }

    /**
     * 根据证件类型枚举类 验证各类ID
     *
     * @param strID      ID
     * @param idTypeEnum 证件类型枚举类
     * @return {@link Boolean }
     */
    public static Boolean isIdCode(String strID, IdTypeEnum idTypeEnum) {
        if (idTypeEnum.equals(IdTypeEnum.IDENTIFICATION_CARD)) {
            return IdcardUtil.isValidCard(strID);
        } else if (idTypeEnum.equals(IdTypeEnum.HONG_KONG_MACAO_PERMIT)) {
            return StrongUtils.isHongKongOrMacaoPermitId(strID);
        } else if (idTypeEnum.equals(IdTypeEnum.TAIWAN_PERMIT)) {
            return StrongUtils.isTaiWanPermitId(strID);
        } else if (idTypeEnum.equals(IdTypeEnum.FOREIGN_PASSPORT)) {
            return StrongUtils.isForeignPassportId(strID);
        } else {
            return false;
        }
    }

    /**
     * 判断是否为 港澳通行证
     *
     * @param strText 测试文本
     * @return {@link Boolean }
     */
    public static Boolean isHongKongOrMacaoPermitId(String strText) {
        return isMatch(strText, "^[HM][0-9]{10}$");
    }

    /**
     * 判断是否为 台胞证号码
     *
     * @param strText 测试文本
     * @return {@link Boolean }
     */
    public static Boolean isTaiWanPermitId(String strText) {
        return isMatch(strText, "^[0-9]{8}$");
    }

    /**
     * 判断是否为 外国护照号码
     *
     * @param strText 测试文本
     * @return {@link Boolean }
     */
    public static Boolean isForeignPassportId(String strText) {
        return isMatch(strText, "^[a-zA-Z0-9]{8,16}$");
    }

    // =================== 断言相关  ===================

    /**
     * 断言正确，否则将错误消息放入map
     *
     * @param expression 表达式
     * @param mapError   错误map
     * @param strKey     错误消息key
     * @param strMessage 错误消息内容
     */
    public static void isTrue(boolean expression, Map<String, String> mapError, String strKey, String strMessage) {
        if (!expression) {
            mapError.put(strKey, strMessage);
        }
    }
}
